#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <time.h>

#include <fcntl.h>
#include <unistd.h>

#include <mpg123.h>

#include "../au.h"
#include "../common.h"
#include "afile.h"

struct afile {
	mpg123_handle *h;
	uint32_t enc;
	long rate;
	int channels;
	int first;
	int fd;
};

enum {
	A_NULL,
	A_ENC,
	A_RATE,
	A_CHAN
};

const struct a_lookup stab[] = {
	{ "enc", A_ENC },
	{ "encoding", A_ENC },
	{ "rate", A_RATE },
	{ "chan", A_CHAN },
	{ "channel", A_CHAN },
	{ "channels", A_CHAN },
	{ NULL, A_NULL }
};

static uint32_t	mpg123_au_enc(int);
static int	au_mpg123_enc(uint32_t);

struct afile *
af_open(const char *file)
{
	int fd;

	if (file == NULL)
		ERR(EINVAL, NULL);
	if ((fd = open(file, O_RDONLY)) < 0)
		return (NULL);
	return (af_fdopen(fd));
}

struct afile *
af_fdopen(int fd)
{
	struct afile *a;
	long rate;
	int chan;
	int enc;

	if ((a = malloc(sizeof(struct afile))) == NULL)
		goto err;
	a->fd = fd;
	a->h = NULL;
	if (mpg123_init() != MPG123_OK)
		return NULL;
	if ((a->h = mpg123_new(NULL, NULL)) == NULL ||
	    mpg123_param(a->h, MPG123_ADD_FLAGS, MPG123_QUIET, 0)
	    != MPG123_OK || mpg123_open_fd(a->h, a->fd) != MPG123_OK)
		goto err;
	if (mpg123_getformat(a->h, &rate, &chan, &enc)
	    != MPG123_OK || rate > (int)(~0U >> 1)) {
		mpg123_close(a->h);
		goto err;
	}
	switch (chan) {
	case MPG123_MONO:
		a->channels = 1;
		break;
	case MPG123_STEREO:
		a->channels = 2;
		break;
	}
	a->rate	= rate;
	a->enc = mpg123_au_enc(enc);
	return (a);
err:
	if (a != NULL) {
		if (a->h != NULL)
			mpg123_delete(a->h);
		if (a->fd >= 0)
			close(a->fd);
		free(a);
	}
	mpg123_exit();
	return (NULL);
}

void
af_close(struct afile *a)
{

	if (a == NULL)
		return;
	if (a->fd >= 0)
		close(a->fd);
	if (a->h != NULL) {
		mpg123_close(a->h);
		mpg123_delete(a->h);
	}
	mpg123_exit();
	free(a);
}


int
af_get(struct afile *a, const char *s, ...)
{
	va_list ap;
	ssize_t r;
	size_t l;
	int n;
	int v;

	if (a == NULL || s == NULL)
		ERR(EINVAL, -1);
	va_start(ap, s);
	for (n = 0; (r = a_lookup(stab, s, &v)) > 0; n++, s += r)
		switch (v) {
		case A_ENC:
			*va_arg(ap, uint32_t *) = a->enc;
			break;
		case A_RATE:
			*va_arg(ap, uint32_t *) = a->rate;
			break;
		case A_CHAN:
			*va_arg(ap, uint32_t *) = a->channels;
			break;
		default:
			goto err;
		}
	if (r == 0) {
		va_end(ap);
		return (n);
	}
err:
	va_end(ap);
	ERR(EINVAL, n);
}

int
af_set(struct afile *a, const char *s, ...)
{
	va_list ap;
	ssize_t r;
	uint32_t t;
	int n;
	int v;

	if (a == NULL || s == NULL)
		ERR(EINVAL, -1);
	va_start(ap, s);
	for (n = 0; (r = a_lookup(stab, s, &v)) > 0; n++, s += r)
		switch (v) {
		case A_ENC:
			a->enc = va_arg(ap, uint32_t);
			break;
		case A_RATE:
			if ((t = va_arg(ap, uint32_t)) > LONG_MAX)
				goto err;
			a->rate = t;
			break;
		case A_CHAN:
			if ((t = va_arg(ap, uint32_t)) == 0 || t > 0)
				goto err;
			a->channels = t;
			break;
		default:
			goto err;
		}
	if (r == 0) {
		va_end(ap);
		return (n);
	}
err:
	va_end(ap);
	ERR(EINVAL, n);
}

ssize_t
af_read(struct afile *a, void *b, size_t s)
{
	size_t r;

	if (a == NULL || b == NULL)
		ERR(EINVAL, -1);
	if (a->first) {
		if (mpg123_format(a->h, a->rate, a->channels,
		    au_mpg123_enc(a->enc)) != MPG123_OK)
			return (-1);
		a->first = 0;
	}
	if (s == 0)
		return (0);
	if (mpg123_read(a->h, b, s, &r) != MPG123_OK)
		ERR(EIO, -1);
	if (au_encnswap(a->enc, b, r) != 0)
		return (-1);
	return (r);
}

struct timespec
af_seek(struct afile *a, struct timespec ts, int whence)
{
	off_t o;

	o = ts.tv_sec * a->rate + ts.tv_nsec * a->rate / 1000000000;
	o = mpg123_seek(a->h, o, whence);
	ts.tv_sec = o / a->rate;
	ts.tv_nsec = o % a->rate * 1000000000 / a->rate;
	return (ts);
}

struct timespec
af_length(struct afile *a)
{
	struct timespec ts;
	off_t o;

	o = mpg123_length(a->h);
	ts.tv_sec = o / a->rate;
	ts.tv_nsec = o % a->rate * 1000000000 / a->rate;
	return (ts);
}

uint32_t
mpg123_au_enc(int enc)
{

	switch (enc) {
	case MPG123_ENC_ULAW_8:
		return (AU_ULAW);
	case MPG123_ENC_UNSIGNED_8:
		/* FALLTHROUGH */
	case MPG123_ENC_SIGNED_8:
		return (AU_PCM8);
	case MPG123_ENC_UNSIGNED_16:
		/* FALLTHROUGH */
	case MPG123_ENC_SIGNED_16:
		return (AU_PCM16);
	case MPG123_ENC_UNSIGNED_24:
		/* FALLTHROUGH */
	case MPG123_ENC_SIGNED_24:
		return (AU_PCM24);
	case MPG123_ENC_UNSIGNED_32:
		/* FALLTHROUGH */
	case MPG123_ENC_SIGNED_32:
		return (AU_PCM32);
	case MPG123_ENC_FLOAT_32:
		return (AU_FLOAT32);
	case MPG123_ENC_FLOAT_64:
		return (AU_FLOAT64);
	case MPG123_ENC_ALAW_8:
		return (AU_ULAW);
	}
	return (0);
}

int
au_mpg123_enc(uint32_t enc)
{

	switch (enc) {
	case AU_ULAW:
		return (MPG123_ENC_ULAW_8);
	case AU_PCM8:
		return (MPG123_ENC_SIGNED_8);
	case AU_PCM16:
		return (MPG123_ENC_SIGNED_16);
	case AU_PCM24:
		return (MPG123_ENC_SIGNED_24);
	case AU_PCM32:
		return (MPG123_ENC_SIGNED_32);
	case AU_FLOAT32:
		return (MPG123_ENC_FLOAT_32);
	case AU_FLOAT64:
		return (MPG123_ENC_FLOAT_64);
	case AU_ALAW:
		return (MPG123_ENC_ALAW_8);
	}
	return (0);
}
